#! /usr/bin/env python3
# -*- coding:utf-8 -*-
"""
UI界面
"""
import os
import sys
from typing import Optional

from PyQt5 import QtCore
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QBrush, QColor, QIcon, QPen, QFont
from PyQt5.QtMultimedia import QSound
from PyQt5.QtWidgets import (QApplication, QMessageBox, QWidget, QLCDNumber, QLabel, 
                            QHBoxLayout, QSlider,QVBoxLayout, QPushButton, QGraphicsScene,
                            QGraphicsView, QGraphicsSimpleTextItem, QRadioButton)

from config import Config
import chessboard, negamax
from util import Point, Chess


class AITask(QtCore.QThread):
    """定义线程类执行AI的算法"""

    finishSignal = QtCore.pyqtSignal(tuple)

    def __init__(self, chessboard: chessboard.ChessBoard, role: int, maxdepth: int=2, parent=None):
        super(AITask, self).__init__(parent)
        self.role = role
        self.maxdepth = maxdepth
        self.chessboard = chessboard

    def run(self):
        """独立线程执行"""
        self.ai = negamax.MinMaxSearcher(self.chessboard, neighbor=1)
        best_score = self.ai.search(self.maxdepth)
        if self.ai.best_move is None:
            raise ValueError("No best move!")
        print(f"AI searched best move: ({self.ai.best_move.row}, {self.ai.best_move.col}), best score: {best_score}")
        self.finishSignal.emit(Point(self.ai.best_move.row, self.ai.best_move.col))


class GoBang(QWidget):
    """UI类"""

    def __init__(self, size:int, title:str="Chess UI", icon:Optional[str]=None):
        super().__init__()
        # =========尺寸定义============
        self.SIZE = size # 棋盘大小
        self._title = title
        self._icon = icon
        # 屏幕的1/1000表示为单位尺寸
        desktop = QApplication.desktop()
        self.UNIT = min(desktop.height(), desktop.width()) // 100
        # 应用大小比例
        ratio = 80
        self.HEIGHT = int(self.UNIT * ratio)
        self.WIDTH = int(self.HEIGHT * 1.2)
        # 棋盘大小
        self.BSIZE = int(self.HEIGHT * 0.8)
        # 网格大小
        self.GRID_SIZE = self.BSIZE / (self.SIZE - 1)
        # 边距
        self.MARGIN_X = int((self.WIDTH * 0.8 - self.BSIZE ) / 2)
        self.MARGIN_Y = int((self.HEIGHT - self.BSIZE ) / 2)
        
        # 逻辑操作相关的变量
        self.chessboard = chessboard.ChessBoard(size=self.SIZE)  # 棋盘类        
        # 存最近的两个点
        self.step = 0
        self.prev_scene_item = None
        # AI已下棋，主要是为了加锁，当值是False的时候说明AI正在思考
        self.ai_down = True
        self.ai_first = False # AI先手
        self.max_depth = 2
        # 计时器
        self.time_step = 0 # 步骤时间
        self.time_total = 0 # 总时间
        self.timer = QTimer(self) # 初始化一个定时器
        self.timer.setInterval(1000)
        self.timer.timeout.connect(self.time_ticking)
        # 初始化UI
        self.initUI()

    def initUI(self):
        """初始化棋盘"""
        # 全局设置
        self.setCursor(Qt.PointingHandCursor)  # 鼠标变成手指形状
        self.setFixedSize(self.WIDTH, self.HEIGHT)
        self.setWindowTitle(f"VIEW")  # 窗口名称
        self.setWindowTitle(self._title)  # 窗口名称
        if self._icon and os.path.isfile(self._icon):
            self.setWindowIcon(QIcon(self._icon))  # 窗口图标
        self.setMouseTracking(True)
        # 上下布局
        self.view_widget()
        hbox = QHBoxLayout(self)
        hbox.addWidget(self.view)
        
        vbox = QVBoxLayout(self)        
        vbox.addLayout(self.display_layout())
        vbox.addStretch(1)
        vbox.addLayout(self.control_layout())
        vbox.addStretch(9)
        hbox.addLayout(vbox)

        self.setLayout(hbox)
        self.show()

    def draw_board(self):
        """绘制棋盘"""
        brush_bg = QBrush(QColor(238, 185, 89, 80))
        brush_p = QBrush(QColor(0, 0, 200, 90))
        pen1 = QPen(QColor(0, 0, 200, 70), 1)
        pen2 = QPen(QColor(0, 0, 200, 70), 2)
        # 背景颜色
        self.scene.setBackgroundBrush(brush_bg)
        # 网格线
        for i in range(self.SIZE):
            hx = int(self.GRID_SIZE * i + self.MARGIN_X)
            y1 = int(self.MARGIN_Y)
            y2 = int(self.MARGIN_Y + self.BSIZE)
            self.scene.addLine(hx, y1, hx, y2, pen1)  # 竖线
            hy = int(self.GRID_SIZE * i + self.MARGIN_Y)
            x1 = int(self.MARGIN_X)
            x2 = int(self.MARGIN_X + self.BSIZE)
            self.scene.addLine(x1, hy, x2, hy, pen1)  # 横线
            if i+1 in [1, self.SIZE, self.SIZE // 2 + 1]:
                # 中心线加粗
                self.scene.addLine(hx, y1, hx, y2, pen2)  # 竖线
                self.scene.addLine(x1, hy, x2, hy, pen2)  # 横线
        # 中心点
        x = int(self.MARGIN_X + round(self.BSIZE / 2))
        y = int(self.MARGIN_Y + round(self.BSIZE / 2))
        x1 = int(self.MARGIN_X + round(self.GRID_SIZE * 3))
        y1 = int(self.MARGIN_Y + round(self.GRID_SIZE * 3))
        x2 = int(self.MARGIN_X + round(self.BSIZE - self.GRID_SIZE * 3))
        y2 = int(self.MARGIN_Y + round(self.BSIZE - self.GRID_SIZE * 3))
        w = 3
        self.scene.addRect(x-w, y-w, 2*w, 2*w, brush=brush_p)        
        self.scene.addRect(x1-w, y1-w, 2*w, 2*w, brush=brush_p)
        self.scene.addRect(x1-w, y2-w, 2*w, 2*w, brush=brush_p)
        self.scene.addRect(x2-w, y1-w, 2*w, 2*w, brush=brush_p)
        self.scene.addRect(x2-w, y2-w, 2*w, 2*w, brush=brush_p)

    def draw_chess(self):
        """绘制棋子"""
        w = int(self.GRID_SIZE * 0.8)
        font = QFont("宋体", 15, 1, False)
        history = self.chessboard.get_history()
        if len(history) > 1: # 上一个落子点重绘，需要改变字体颜色
            if self.prev_scene_item:
                self.scene.removeItem(self.prev_scene_item)
            pprev = history[-2]
            row, col = pprev.row, pprev.col
            x, y = self.coord_map2pixel(row, col)
            if self.chessboard.get(pprev) == Chess.BLACK:
                bcolor = QColor("#363636")
                fcolor = QColor("#FFFAFA")
            else:
                bcolor = QColor("#FFFFF0")
                fcolor = QColor("#1C1C1C")
            # 数字序号
            text = QGraphicsSimpleTextItem()
            text.setFont(font)
            pen = QPen(fcolor, 1)
            text.setPen(pen)
            text.setText(str(self.step - 1))
            bias = 8 if self.step - 1 < 9 else 4
            x, y = x - w//bias, y - w//4
            text.setPos(x, y)
            self.scene.addItem(text)
        if history:  # 最后一个落子点着重显示
            prev = history[-1]
            row, col = prev.row, prev.col
            x, y = self.coord_map2pixel(row, col)
            bcolor = QColor("#363636") if self.chessboard.get(prev) == Chess.BLACK else QColor("#FFFFF0")
            fcolor = QColor("#EE6363")
            brush = QBrush(bcolor)
            self.scene.addEllipse(x - w//2, y - w//2, w, w, pen=QPen(Qt.black, 1), brush=brush)
            # 数字序号
            text = QGraphicsSimpleTextItem()
            text.setFont(font)
            pen = QPen(fcolor, 2)
            text.setPen(pen)
            text.setText(str(self.step))
            bias = 8 if self.step < 9 else 4
            x, y = x - w//bias, y - w//4
            text.setPos(x, y)
            self.prev_scene_item = text
            self.scene.addItem(text)

    def view_widget(self):
        """初始化绘图组件"""
        self.scene = QGraphicsScene()
        self.view = QGraphicsView(self.scene)
        # 绘制棋盘
        self.draw_board()
        # 绘制棋子
        self.draw_chess()
        
        return self.view

    def coord_map2pixel(self, row, col):
        """从 chessMap 里的逻辑坐标到 UI 上的绘制坐标的转换"""
        x = col * self.GRID_SIZE + self.MARGIN_X
        y = row * self.GRID_SIZE + self.MARGIN_Y
        return int(x), int(y)

    def coord_pixel2map(self, x:int, y:int):
        """从 UI 上的绘制坐标到 chessMap 里的逻辑坐标的转换"""
        row = int(round((y - self.MARGIN_Y) / self.GRID_SIZE))
        col = int(round((x - self.MARGIN_X) / self.GRID_SIZE))
        # 排除边缘位置导致坐标越界
        if row < 0 or row >= self.SIZE or col < 0 or col >= self.SIZE:
            return None, None
        return row, col

    def display_layout(self):
        """右侧可视化组件"""
        vbox = QVBoxLayout(self)
        # 思考提示
        hbox_tip = QHBoxLayout(self)
        self.tip = QLabel(self)
        self.tip.setFont(QFont("宋体", 12))
        self.tip.setStyleSheet("color:gray;")
        self.tip.setText("等待开始游戏！")
        self.tip.setAlignment(Qt.AlignRight)
        hbox_tip.addWidget(self.tip)
        vbox.addLayout(hbox_tip)
        # 局时组件
        hbox_total = QHBoxLayout(self)
        label_total = QLabel(self)
        label_total.setFont(QFont("宋体", 20))
        label_total.setText("局时  ")
        hbox_total.addWidget(label_total)
        self.lcd_total = QLCDNumber(self)
        self.lcd_total.setDigitCount(8)
        self.lcd_total.setMinimumHeight(self.UNIT * 4)
        self.lcd_total.setMinimumWidth(self.UNIT * 12)
        self.lcd_total.setMode(QLCDNumber.Dec)
        self.lcd_total.setSegmentStyle(QLCDNumber.Flat)
        self.lcd_total.setStyleSheet("border :1px solid green;color:red;")
        hbox_total.addWidget(self.lcd_total)
        vbox.addLayout(hbox_total)

        return vbox

    def control_layout(self):
        """操控界面"""
        vbox = QVBoxLayout(self)
        font = QFont("宋体", 16, 5)
        style = "background-color: #7EC0EE;border-radius: 10px; border: 1px groove gray;border-style: outset;color:white"

        hbox = QHBoxLayout(self)
        self.ai_btn = QRadioButton(self)
        self.ai_btn.setMinimumSize(self.UNIT * 2, self.UNIT * 2)
        self.ai_btn.setText("AI先手")
        self.ai_btn.setChecked(self.ai_first)
        self.ai_btn.setFont(font)
        self.ai_btn.clicked.connect(self.click_ai_btn)
        hbox.addWidget(self.ai_btn)

        self.human_btn = QRadioButton(self)
        self.human_btn.setMinimumSize(self.UNIT * 2, self.UNIT * 2)
        self.human_btn.setText("人类先手")
        self.human_btn.setFont(font)
        self.human_btn.setChecked(not self.ai_first)
        hbox.addWidget(self.human_btn)
        self.human_btn.clicked.connect(self.click_human_btn)
        vbox.addLayout(hbox)
        vbox.addSpacing(self.UNIT * 2)

        # AI搜索深度
        self.label_depth = QLabel('AI搜索深度:  2')
        self.label_depth.setFont(font)
        vbox.addWidget(self.label_depth)
        vbox.addSpacing(self.UNIT)
        self.slider = QSlider(Qt.Horizontal)
        self.slider.setMinimum(1)
        self.slider.setMaximum(12)
        self.slider.setSingleStep(1)  # 步长
        self.slider.setValue(2)  # 当前值
        self.slider.setMaximumWidth(int(self.UNIT * 20))
        self.slider.setTickInterval(1)  # 设置刻度的间隔
        self.slider.valueChanged.connect(self.set_depth)
        vbox.addWidget(self.slider)
        vbox.addSpacing(self.UNIT * 2)

        # 开始/重置按钮
        hbox_op = QHBoxLayout(self)
        self.start_btn = QPushButton(self)
        self.start_btn.setFont(font)
        self.start_btn.setStyleSheet(style)
        hratio = 4
        self.start_btn.setMinimumSize(self.UNIT * hratio, self.UNIT * hratio)
        self.start_btn.setText("开始")
        self.start_btn.clicked.connect(self.click_start)
        hbox_op.addWidget(self.start_btn)

        hbox_op.addSpacing(self.UNIT)
        self.reset_btn = QPushButton(self)
        self.reset_btn.setFont(font)
        self.reset_btn.setStyleSheet(style)
        self.reset_btn.setMinimumSize(self.UNIT * hratio, self.UNIT * hratio)
        self.reset_btn.setText("重置")
        self.reset_btn.clicked.connect(self.click_reset)
        hbox_op.addWidget(self.reset_btn)
        vbox.addLayout(hbox_op)
        
        return vbox

    def set_depth(self):
        """搜索深度"""
        if not self.slider.isEnabled():
            return
        self.max_depth = self.slider.value()
        self.label_depth.setText(f"AI搜索深度:  {self.max_depth}")

    def click_ai_btn(self):
        """切换按钮状态"""
        self.ai_first = True
        self.ai_btn.setChecked(self.ai_first)
        self.human_btn.setChecked(not self.ai_first)
    
    def click_human_btn(self):
        """切换按钮状态"""
        self.ai_first = False
        self.ai_btn.setChecked(self.ai_first)
        self.human_btn.setChecked(not self.ai_first)

    def time_ticking(self):
        """计时器"""
        self.time_step += 1 # 步骤时间
        self.time_total += 1 # 总时间

        self.lcd_total.display(f"{self.time_total:0>3d}:{self.time_step:0>3d}")

    def mousePressEvent(self, e):
        """人类玩家下棋"""
        if self.start_btn.isEnabled():
            QMessageBox.warning(self, "Warning", "请先开始游戏!")
            return
        if e.button() == Qt.LeftButton: # 按下鼠标左键
            if not self.ai_down: # AI 还未下棋
                QMessageBox.warning(self, "Warning", "Not Your turn!")
                return
            row, col = self.coord_pixel2map(e.x(), e.y())  # 对应棋盘坐标
            if row is None or col is None:  # 棋子落在棋盘外
                print("Out of chessboard!")
                return
            point = Point(row, col)
            if not self.move_chess(point, ai=False): # 还未结束
                self.AI_play()

    def move_chess(self, point:Point, ai=True):
        """走子，更新对应状态"""
        if not self.chessboard.is_empty(point): # 棋子没有落在空白处
            raise ValueError(f"Position {point} is not empty!")
        self.chessboard.go(point)
        self.step += 1
        self.ai_down = ai
        self.draw_chess()
        self.view.viewport().update()
        self.update()
        self.timer.stop()
        self.time_step = 0
        self.timer.start() # 重新计时
        self.tip.setText(f"{'AI' if not ai else '人类'} 正在思考...")

        return self.check_win()

    def AI_play(self):
        """AI走子"""
        self.ai_down = False
        self.timer.start()
        chess = Chess.BLACK if self.ai_first else Chess.WHITE
        self.ai = AITask(self.chessboard, chess, self.max_depth)  # 新建线程对象，传入棋盘参数
        self.ai.finishSignal.connect(self.move_chess)  # 结束线程，传出参数
        self.ai.start() # 后台线程执行思考

    def human_play(self):
        """人类下棋"""
        self.timer.start()

    def check_win(self):
        """判断是否赢棋"""
        if self.chessboard.check_five():  # 判断输赢
            self.ai_down = True
            self.gameover(self.chessboard.last)
            return True
        
    def click_start(self):
        """开始游戏"""
        if not self.start_btn.isEnabled():
            return
        self.slider.setEnabled(False)
        self.start_btn.setEnabled(False)
        self.reset_btn.setEnabled(True)
        self.human_btn.setEnabled(False)
        self.ai_btn.setEnabled(False)

        if self.ai_first: # AI先手
            self.AI_play()
        else: # 人类先手
            self.human_play()

    def click_reset(self):
        """清空"""
        if not self.reset_btn.isEnabled():
            return
        if not self.ai_down:
            QMessageBox.warning(self, "Warning", "AI还在思考，请稍候！")
            return
        self.slider.setEnabled(True)
        self.start_btn.setEnabled(True)
        self.reset_btn.setEnabled(False)
        self.human_btn.setEnabled(True)
        self.ai_btn.setEnabled(True)
        self.max_depth = 2
        self.slider.setValue(self.max_depth)
        self.label_depth.setText(f"AI搜索深度:  {self.max_depth}")
        self.timer.stop()
        self.time_step = 0
        self.time_total = 0
        self.lcd_total.display("")
        self.tip.setText("等待开始游戏")
        self.chessboard.reset()
        self.step = 0
        self.prev_scene_item = None
        self.scene.clear()
        self.draw_board()
        self.view.viewport().update()
        self.update()

    def gameover(self, winner):
        """游戏结束"""
        human_chess = Chess.WHITE if self.ai_first else Chess.BLACK
        if winner == human_chess:
            reply = QMessageBox.question(self, 'You Win!', 'Congratulations, you win! Continue?',
                                         QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        else:
            reply = QMessageBox.question(self, 'You Lost!', 'Sorry, you lost! Continue?',
                                         QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

        if reply == QMessageBox.Yes:  # 复位
            self.click_reset()
        else:
            self.click_reset()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = GoBang(Config.SIZE)
    sys.exit(app.exec_())
